from datetime import datetime
from datetime import timedelta
from django.db.models import Q
from django.utils import timezone
from rest_framework.exceptions import ValidationError
from apps.core.utils import date_time as datetime_helper

from apps.booking.models import Booking, Appointment
from apps.booking.types import BookingStatusChoices


def validate_booking(data):
    ONE_HOUR_SECONDS = 3600
    FIFTY_MINUTE_SECONDS = 900

    date = data.get('start_date')
    end_time = data.get('end_time')
    start_time = data.get('start_time')
    # room = data.get('room')
    provider_id = data.get('provider')
    select_dates = False

    if 'select_dates' in data and data['select_dates'] is True:
        select_dates = True
        if 'selected_dates' not in data:
            raise ValidationError({'msg': 'List of Dates is required'})
    start_datetime = timezone.make_aware((datetime.combine(date.date(), datetime.strptime(start_time, "%H:%M:%S").time())),
                                         timezone.get_current_timezone())
    end_datetime = timezone.make_aware((datetime.combine(date.date(), datetime.strptime(end_time, "%H:%M:%S").time())),
                                       timezone.get_current_timezone())

    duration = (end_datetime - start_datetime).total_seconds()


    if start_time >= end_time:
        raise ValidationError({"msg": "Start Time must be lower than end Time"})
    if start_datetime < timezone.now():
        raise ValidationError({"msg": "Can not book in the past"})
    thirty_days = timezone.now() + timedelta(days=60)
    if start_datetime >= thirty_days:
        raise ValidationError({"msg": "Start Date must be less than Sixty days"})

    if duration < ONE_HOUR_SECONDS:
        raise ValidationError({"msg": "Duration must be at least one hour"})
    if duration % FIFTY_MINUTE_SECONDS != 0:
        raise ValidationError({"msg": "Booking duration must be 15-minutes increment"})
    invalid_provider_booking = count_valid_provider_in_other_booking(
        provider_id, date, start_time, end_time)

    invalid_provider_appointment = count_valid_provider_in_other_appointment(
        provider_id, date, start_time, end_time)

    if invalid_provider_booking > 0:
        raise ValidationError(
            'This provider is in the other booking in this time.')

    if invalid_provider_appointment > 0:
        raise ValidationError(
            'This provider is in the other appointment in this time.')

    if select_dates is True:
        selected_dates = data.get('selected_dates')
        invalid_provider_booking = count_valid_provider_in_other_booking_repeat(
            provider_id, selected_dates, start_time, end_time)

        invalid_provider_appointment = count_valid_provider_in_other_appointment_repeat(
            provider_id, selected_dates, start_time, end_time)
        if invalid_provider_booking > 0:
            raise ValidationError(
                'This provider is in the other booking in this time.')

        if invalid_provider_appointment > 0:
            raise ValidationError(
                'This provider is in the other appointment in this time.')

    return data


def count_valid_provider_in_other_booking(
        provider_id, start_date, start_time, end_time):
    query = Booking.objects.all()
    query = query.filter(
        start_date__date=start_date.date()).exclude(
        status=BookingStatusChoices.CANCELED.value
    )
    query = query.filter(
        Q(start_time__range=(start_time, end_time)) |
        Q(end_time__range=(start_time, end_time))
    )
    query = query.exclude(
        Q(end_time=start_time) |
        Q(start_time=end_time)
    )

    return query.count()


def count_valid_provider_in_other_appointment(
        provider_id, start_date, start_time, end_time):
    query = Appointment.objects.all()
    query = query.filter(
        start_date__date=start_date.date()).exclude(
        status=BookingStatusChoices.CANCELED.value
    )
    query = query.filter(
        Q(start_time__range=(start_time, end_time)) |
        Q(end_time__range=(start_time, end_time))
    )
    query = query.exclude(
        Q(end_time=start_time) |
        Q(start_time=end_time)
    )

    return query.count()


def count_valid_provider_in_other_booking_repeat(
        provider_id, start_date, start_time, end_time):
    query = Booking.objects.all()
    query = query.filter(
        start_date__date__in=start_date).exclude(
        status=BookingStatusChoices.CANCELED.value
    )
    query = query.filter(
        Q(start_time__range=(start_time, end_time)) |
        Q(end_time__range=(start_time, end_time))
    )
    query = query.exclude(
        Q(end_time=start_time) |
        Q(start_time=end_time)
    )

    return query.count()


def count_valid_provider_in_other_appointment_repeat(
        provider_id, start_date, start_time, end_time):
    query = Appointment.objects.all()
    query = query.filter(
        start_date__date__in=start_date).exclude(
        status=BookingStatusChoices.CANCELED.value
    )
    query = query.filter(
        Q(start_time__range=(start_time, end_time)) |
        Q(end_time__range=(start_time, end_time))
    )
    query = query.exclude(
        Q(end_time=start_time) |
        Q(start_time=end_time)
    )

    return query.count()
